昨天我們看了 node 上面的各種 status 描述,而這些 status 當不健康時又會如何呢?
kubelet 會收集各種 nodes 的資訊,並且依據配置的閥值啟動 Eviction(驅逐) 釋放資源
因為 node pressure 而導致的驅逐,kubelet 會把 pods 設定為 Failed 的狀態,並且關閉pods
Node-pressure 與 API 所觸發的驅逐是不同的 (e.g. drain node),其中最大的差異就是 node-pressure 驅逐並 “不一定” 會遵循 terminationGracePeriodSeconds
的秒數,兩種情況:
eviction-max-pod-grace-period
配置的秒數當資源不足時,kubelet 會嘗試先獲取 node-level 的資源,然後才會嘗試刪除使用者建立的 pods,例如當 disk 空間不足時,會嘗試刪除 node 上未使用到的 container images
而若是 pods 有由 controller 管理 (e.g. deployment) 的話,controller-manger
會嘗試建立新的 pods 以取代掉被驅逐的 pods
因為 static pods 就是會強制放在該 node 上,因此 kubelet 會嘗試重建,但 kubelet 依然會嘗試讓所有的 static pods 能夠正確運作。
關於 Eviction 的配置,kubelet 有三個:
nodeStatusUpdateFrequency
參數)Eviction signals
在節點的硬體資源上,大致上可以分三大類:
memory.availble
{nodefs, imagefs, containerfs}.{avilable,inodesFree}
pid.available
註:contiainerfs 是相對較新的功能,在目前的 k8s 1.31 版本中必須開啟
KubeletSeparateDiskGC
功能才能使用,且目前僅能用於 CRI-O 的 container runtime 上
在 linux 的 node 上,memory.available
是由 cgroupfs 衍伸出的,這個數值會與 free -m
輸出的結果有點差異
官方文件提供了以下 script 模擬 kubelet 查詢 cgroupv2 的記憶體計算方式:
#!/bin/bash
# This script reproduces what the kubelet does
# to calculate memory.available relative to kubepods cgroup.
# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/kubepods.slice/memory.current)
memory_total_inactive_file=$(cat /sys/fs/cgroup/kubepods.slice/memory.stat | grep inactive_file | awk '{print $2}')
memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
memory_working_set=0
else
memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi
memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))
echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_file"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"
在我的 master1 機器上,執行結果如下:
memory.capacity_in_bytes 4056092672
memory.usage_in_bytes 1837711360
memory.total_inactive_file 434024448
memory.working_set 1403686912
memory.available_in_bytes 2652405760
memory.available_in_kb 2590240
memory.available_in_mb 2529
nodefs
:node 主要的儲存空間,用於 local disk volume, emptyDir, logs, ephemeral storage 以及更多imagefs
:container runtimes 用來儲存 container image 與 container 可寫層containerfs
:與 nodefs 類似,但是用於 container 寫入的資訊。雖然上述分成三種,不過如果沒有特別切割系統空間的話,也可能會被看做同一份空間:
/var/lib/containerd
有另外切出) ,且 imagefs 於 root filesystem 切開,又被稱作 “split disk”kubelet 將會自動檢測檔案系統的情況,且忽略其餘的 node filesystems,因此就算其他的空間達到上限,也不會觸發 “Filesystem signals”
設定觸發上限分成 sort 與 hard,配置上的格式為:[eviction-signal][operator][quantity]
eviction-signal
:就是上面的那些operator
(預算符):大於小於等等quantity
(數量):可以配置整數 (e.g. 1Gi
) 或百分比 (%
),但只能擇一Soft 顧名思義,就是比較軟性的限制,通常會比起 Hard 不那麼嚴苛
當觸發 Soft 上限時,被驅逐的 pods 會以 graceful 的方式被關閉
透過以下配置來設定 soft eviction thresholds:
eviction-soft
:配置 thresholds,比如: memory.available <1.5Gieviction-soft-grace-period
:當觸發對應條件時,會等多久才開始動作,如 memory.available=1m30s
代表該狀態持續 1分30秒 後才會觸發eviction-max-pod-grace-period
:當觸發驅逐的 pods 最多可以忍受關閉多久時間,超過之後就會強制刪除了觸發後就沒有 grace period 的條件,會立刻的把 pods 刪除 (官方文件這邊寫為了獲取 “飢餓的資源”)
因為不用配置 grace period 的關係,hard eviction 僅有一個參數為:eviction-hard
,直接配置 hard eviction threshold (如 memory.available<1Gi
)
以下是一些預設的條件:
memory.available<100Mi
(Linux nodes)memory.available<500Mi
(Windows nodes)nodefs.available<10%
imagefs.available<15%
nodefs.inodesFree<5%
(Linux nodes)imagefs.inodesFree<5%
(Linux nodes)要特別注意的是,僅有完全沒配置的情況才會套用預設值,若其中之一有配置的話,則其他未配置的參數會被改為 0 → 因此要不就是完全沒配置,不然就是全部都要記得配置!!
containerfs
比較特別,她的 thresholds 會依據磁碟切割分為:
containerfs
與 nodefs
一樣containerfs
與 imagefs
一樣跟昨日看的 condition 是一樣的,基本上就是相對應的資源:
MemoryPressure
:memory.availableDiskPressure
:其他所有磁碟相關的PIDPressure
:pid.availablekubelet 預設每 10s 會更新這些狀態
Node condition oscillation 指的是 soft eviction thresholds 的狀態不斷來回,可能會導致錯誤的 eviction 情況
可以配置 eviction-pressure-transition-period
參數,讓 kubelet 必須等待一段時間後,才會開始進行 sort eviction,預設為 5m
在驅逐 pods 之前,kubelet 會先嘗試從 node-level 去獲取資源 (e.g DiskPresure)
當 Node-level 的資源都無法滿足資源需求時,就會開始嘗試刪除由 user 建立的 pods,順序為:
kubelet 會針對 pods 定義一些等級:
BestEffort
or Burstable
且實際使用量大於 request 的 pods 會優先被驅逐。Guaranteed
pods and Burstable
pods 且實際使用量低於 request 的將會最後被考慮驅逐。但若是 EphemeralStorage (DiskPressure) 的情況則上述的優先順序無效
若希望 static pods 不要受到驅逐影響,則可以配置 priority 欄位 (priorityClassName 欄位在 static pod 沒作用)
可以看看 apiserver 的 yaml,也有配置 priority
欄位:
❯ cat /etc/kubernetes/manifests/kube-apiserver.yaml
priority: 2000001000
priorityClassName: system-node-critical
Minimum eviction reclaim 是設定一定的值,使得每次觸發 eviction 時,獲取足夠的資源,而非超過 threshold 而已,範例配置如下:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
memory.available: "500Mi"
nodefs.available: "1Gi"
imagefs.available: "100Gi"
evictionMinimumReclaim:
memory.available: "0Mi"
nodefs.available: "500Mi"
imagefs.available: "2Gi"
當 nodefs
觸發時,代表總空間 < 1Gi,此時會最少一次獲取 500Mi 的空間,使得空間至少有 1.5Gi (1Gi + 500Mi)
而 imagefs
的範例,當觸發時代表 imagefs
< 100Gi,kubelet 會嘗試獲取 2Gi 的空間,使得空間至少有 102Gi
但當 kubelet 發現獲取資源無法達到 minimum 的情況時,則不會做任何動作
該值對所有資源的預設為 0
當以下情況時:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
以上配置會預留 1.5G 的 memory 給系統使用,並且當 pods 占用 memory 過多導致剩餘 500 MiB 時觸發驅逐。
因為 kubelet 目前都盡量要使用 config 的方式來帶入參數,因此若要測試的話,可以調整 /var/lib/kubelet/config.yaml
:
systemReserved:
memory: 1G
❯ sudo systemctl restart kubelet
改完之後會發現使用 kubectl describe
顯示 memory 可分配的值變少了:
❯ k describe no k8s-master1 | grep memory
# 改之前,上面是總空間,下面是可分配空間
memory: 3961028Ki
memory: 3858628Ki
# 改之後
memory: 3961028Ki
memory: 2951235072
這個配置目前只能配置 CPU 與 memory 兩種資源。
因為某些 DaemonSet 是重要的服務 (e.g. CNI),因此可能不會希望這些 pods 能不要那麼容易被驅逐
方法就如同上面討論的,將 pods 的 priority 調整高一點,就可以降低 damonSet 的 pods 被驅逐的機會
反言之,若 DaemonSet 的 Pods 並沒有那麼重要,則可以把 priority 調低一點或者用預設的,就可以優先把資源給其他 pods 使用
預設情況下,kube-proxy 即是以 daemonset 的方式來跑的,也可以發現有配置 priorityClassName: system-node-critical
,使得 kube-proxy
有著高優先度。
❯ k get ds -n kube-system kube-proxy -o yaml | grep priority
...
priorityClassName: system-node-critical
看完這篇之後,就對 kubelet 的 GC 機制比較了解了一些,搭配上昨天的 node status,就可以比較理解當資源吃緊時,kubelet 會以多少的頻率發現問題並處理等等等
針對一些比較重要的服務也可以透過配置 priority 使其高於一般的 pods,就可以存活比較久一點啦 ~
https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/
https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/